package com.bzframework.pro.authorize.aop;

import com.bzframework.pro.authorize.annotation.Authorization;
import com.bzframework.pro.authorize.context.ReactiveUserTokenContextHolder;
import com.bzframework.pro.authorize.enums.UserRoleEnum;
import com.bzframework.pro.authorize.token.UserToken;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.hswebframework.web.exception.BusinessException;
import org.reactivestreams.Publisher;
import org.springdoc.webflux.api.MultipleOpenApiResource;
import org.springdoc.webflux.ui.SwaggerConfigResource;
import org.springdoc.webflux.ui.SwaggerWelcomeCommon;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;

/**
 * @Author bingzhong
 * @Date 2024/11/21 16:34
 * @Description
 */
@Slf4j
public class AopAuthorizationController extends StaticMethodMatcherPointcutAdvisor implements MethodInterceptor {

    public AopAuthorizationController() {
        setAdvice(this);
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        Authorization authorization = AnnotationUtils.findAnnotation(targetClass, Authorization.class);
        if (authorization != null && authorization.ignore()) {
            return false;
        }
        return AnnotationUtils.findAnnotation(targetClass, RestController.class) != null
                || AnnotationUtils.findAnnotation(targetClass, Controller.class) != null
                || AnnotationUtils.findAnnotation(targetClass, RequestMapping.class) != null;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        if (aboutApiDocument(methodInvocation)) {
            return methodInvocation.proceed();
        }

        Authorization methodAuthorization = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), Authorization.class);
        if (methodAuthorization != null && methodAuthorization.ignore()) {
            return methodInvocation.proceed();
        }

        Class<?> returnType = methodInvocation.getMethod().getReturnType();
        if (Publisher.class.isAssignableFrom(returnType)) {
            Publisher publisher = doAuthorize(methodInvocation);
            if (Mono.class.isAssignableFrom(returnType)) {
                return Mono.from(publisher);
            } else if (Flux.class.isAssignableFrom(returnType)) {
                return Flux.from(publisher);
            }
            throw new UnsupportedOperationException("unsupported reactive type:" + returnType);
        }

        // 验证非响应式方法
//        ParsedToken parsedToken = UserTokenHolder.currentToken();
//        if (parsedToken.getToken() == null) {
//            throw new UnAuthorizedException.NoStackTrace();
//        }
        // TODO 暂时解决不了这个问题
//        UserToken userToken = userTokenManager
//                .getUserToken(parsedToken.getToken())
//                .blockOptional()
//                .orElse(null);
//        if (userToken == null) {
//            throw new UnAuthorizedException.NoStackTrace();
//        }

        return methodInvocation.proceed();
    }

    @SneakyThrows
    private <T> T doProceed(MethodInvocation invocation) {
        return (T) invocation.proceed();
    }

    private Publisher<?> doAuthorize(MethodInvocation methodInvocation) {

        return ReactiveUserTokenContextHolder.get()
                                             .switchIfEmpty(Mono.error(UnAuthorizedException.NoStackTrace::new))
                                             .flatMap(userToken -> isAdmin(methodInvocation, userToken))
                                             .flatMapMany(userToken -> doProceed(methodInvocation));

    }

    /**
     * 判断接口访问是否需要admin权限
     * @param methodInvocation
     * @param userToken
     * @return
     */
    private static Mono<UserToken> isAdmin(MethodInvocation methodInvocation, UserToken userToken) {
        Method method = methodInvocation.getMethod();
        Authorization methodAuthorization = method.getAnnotation(Authorization.class);
        if (methodAuthorization != null) {
            if (methodAuthorization.admin()) {
                String role = userToken.getRole();
                if (!UserRoleEnum.admin.name().equals(role)) {
                    return Mono.error(new BusinessException("no admin auth"));
                }
            }
        }
        return Mono.just(userToken);
    }

    private boolean aboutApiDocument(MethodInvocation methodInvocation) {
        return methodInvocation.getMethod().getDeclaringClass() == SwaggerConfigResource.class
                || SwaggerWelcomeCommon.class.isAssignableFrom(methodInvocation.getMethod().getDeclaringClass())
                || MultipleOpenApiResource.class.isAssignableFrom(methodInvocation.getMethod().getDeclaringClass());
    }
}
